package cn.ywang.spring.framework.context;

import cn.ywang.spring.framework.annotation.Autowired;
import cn.ywang.spring.framework.annotation.Controller;
import cn.ywang.spring.framework.annotation.Service;
import cn.ywang.spring.framework.aop.config.AopConfig;
import cn.ywang.spring.framework.aop.framework.AdvisedSupport;
import cn.ywang.spring.framework.aop.framework.AopProxy;
import cn.ywang.spring.framework.aop.framework.CglibAopProxy;
import cn.ywang.spring.framework.aop.framework.JdkDynamicAopProxy;
import cn.ywang.spring.framework.beans.BeanWrapper;
import cn.ywang.spring.framework.beans.factory.BeanFactory;
import cn.ywang.spring.framework.beans.factory.config.BeanDefinition;
import cn.ywang.spring.framework.beans.factory.config.BeanPostProcessor;
import cn.ywang.spring.framework.beans.factory.support.BeanDefinitionReader;
import cn.ywang.spring.framework.beans.factory.support.DefaultListableBeanFactory;
import cn.ywang.spring.utils.StringUtils;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author BeatificWang
 * @date 2019/4/21 22:50
 */
public class ApplicationContext extends DefaultListableBeanFactory implements BeanFactory {

	private String[] configLocations;

	private BeanDefinitionReader reader;

	/**
	 * Cache of singleton objects: bean name --> bean instance
	 */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	private final Map<String, BeanWrapper> fac = new ConcurrentHashMap<>(256);

	public ApplicationContext(String configLocation) {
		this(new String[]{configLocation});
	}

	public ApplicationContext(String[] configLocations) {
		this.configLocations = configLocations;
		this.refresh();
	}

	@Override
	public void refresh() {
		// 1.定位配置文件
		this.reader = new BeanDefinitionReader(this.configLocations);

		// 2.扫描相关类，封装为BeanDefinition
		List<BeanDefinition> beanDefinitions = this.reader.loadBeanDefinitions();

		// 3.注册，把BeanDefinition注册至IOC容器（伪IOC容器）
		this.registerBeanDefinition(beanDefinitions);
		// 4.非延时加载的Bean，初始化
		this.doAutowired();
	}

	/**
	 * 只处理非延时加载
	 */
	private void doAutowired() {
		for (Map.Entry<String, BeanDefinition> entry : this.beanDefinitionMap.entrySet()) {
			String beanName = entry.getKey();
			if (!entry.getValue().isLazyInit()) {
				this.getBean(beanName);
			}
		}
	}

	/**
	 * 注册
	 *
	 * @param beanDefinitions
	 */
	private void registerBeanDefinition(List<BeanDefinition> beanDefinitions) {
		for (BeanDefinition beanDefinition : beanDefinitions) {
			if (!super.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
				super.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
			}
		}
	}

	@Override
	public Object getBean(String name) {
		// 初始化
		BeanDefinition definition = this.beanDefinitionMap.get(name);
		BeanWrapper beanWrapper = this.instantiateBean(name, definition);
		// 保存
		//		if (this.fac.containsKey(name)) {
		//			throw new RuntimeException("已存在同名Bean：" + name);
		//		}
		this.fac.put(name, beanWrapper);
		// 注入
		this.populateBean(name, definition, beanWrapper);
		return this.fac.get(name).getWrappedObject();
	}

	/**
	 * 初始化Bean
	 *
	 * @param name
	 * @param beanDefinition
	 */
	private BeanWrapper instantiateBean(String name, BeanDefinition beanDefinition) {
		// 取出类名
		String className = beanDefinition.getBeanClassName();
		// 反射实例化
		Object instance = null;
		Class<?> clazz = null;
		try {
			if (this.singletonObjects.containsKey(className)) {
				instance = this.singletonObjects.get(className);
			}
			else {
				clazz = Class.forName(className);
				instance = clazz.newInstance();

				AdvisedSupport config = this.instantiateAopConfig(beanDefinition);
				config.setTargetClass(clazz);
				config.setTarget(instance);

				//符合PointCut的规则时代理对象
				if (config.pointCutMatch()) {
					instance = this.createProxy(config).getProxy();
				}

				this.singletonObjects.put(className, instance);
				this.singletonObjects.put(beanDefinition.getFactoryBeanName(), instance);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		// 初始化前调用处理器
		BeanPostProcessor processor = new BeanPostProcessor();
		processor.postProcessBeforeInitialization(instance, name);
		// 封装为BeanWrapper
		BeanWrapper beanWrapper = new BeanWrapper();
		beanWrapper.setRootObject(instance);
		beanWrapper.setWrappedObject(instance);
		beanWrapper.setWrappedClass(clazz);
		// 初始化后调用处理器
		processor.postProcessAfterInitialization(instance, name);
		return beanWrapper;
	}

	/**
	 * 创建代理
	 *
	 * @param config
	 * @return
	 */
	private AopProxy createProxy(AdvisedSupport config) {
		Class targetClass = config.getTargetClass();
		if (targetClass.getInterfaces().length > 0) {
			return new JdkDynamicAopProxy(config);
		}
		return new CglibAopProxy(config);
	}

	/**
	 * 注入Bean
	 *
	 * @param beanName
	 * @param mbd
	 * @param bw
	 */
	protected void populateBean(String beanName, BeanDefinition mbd, BeanWrapper bw) {
		Object instance = bw.getWrappedObject();
		Class<?> clazz = bw.getWrappedClass();
		if (!clazz.isAnnotationPresent(Controller.class) && !clazz.isAnnotationPresent(Service.class)) {
			return;
		}
		// 获得所有字段
		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			Autowired autowired = field.getAnnotation(Autowired.class);
			if (autowired == null) {
				continue;
			}
			String autowiredName = autowired.value();
			if (StringUtils.isBlank(autowiredName)) {
				// 类型名
				autowiredName = field.getType().getName();
			}
			field.setAccessible(true);
			try {
				field.set(instance, this.fac.get(autowiredName).getWrappedObject());
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 获得IOC容器中所有的bean名称
	 *
	 * @return
	 */
	public String[] getBeanDefinitionNames() {
		return this.beanDefinitionMap.keySet().toArray(new String[this.beanDefinitionMap.size()]);
	}


	/**
	 * 获得IOC容器中bean数量
	 *
	 * @return
	 */
	public int getBeanDefinitionCount() {
		return this.beanDefinitionMap.size();
	}

	public Properties getConfig() {
		return this.reader.getContextConfig();
	}

	private AdvisedSupport instantiateAopConfig(BeanDefinition gpBeanDefinition) {
		AopConfig config = new AopConfig();
		config.setPointCut(this.getConfig().getProperty("pointCut"));
		config.setAspectClass(this.getConfig().getProperty("aspectClass"));
		config.setAspectBefore(this.getConfig().getProperty("aspectBefore"));
		config.setAspectAfter(this.getConfig().getProperty("aspectAfter"));
		config.setAspectAfterThrow(this.getConfig().getProperty("aspectAfterThrow"));
		config.setAspectAfterThrowingName(this.getConfig().getProperty("aspectAfterThrowingName"));
		return new AdvisedSupport(config);
	}
}